package net.es.weathermap.metric;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.List;

import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;

import net.es.weathermap.beans.AggrConnection;
import net.es.weathermap.beans.Connection;
import net.es.weathermap.beans.Domain;
import net.es.weathermap.beans.Hub;
import net.es.weathermap.beans.IfceConnection;
import net.es.weathermap.beans.Node;

import org.apache.log4j.*;

public class UtilizationMP implements MetricProvider {

    protected static Domain domain = null;
    protected static HashMap<String, List<String>> namesToIfceIds;
    protected static HashMap<String, Integer> namesToCapacity;
    protected static HashMap<String, Connection> ifceNamesToConnections;
    protected ArrayList<String> metricNames;
    protected static ArrayList<String> staleIds = new ArrayList<String>();
    private static Logger LOG = Logger.getLogger(UtilizationMP.class);

    /* override me */
    protected MetricCache getCache() {
        return null;
    }

    public static ArrayList<String> getStaleIds() {
        return staleIds;
    }

    public List<String> getMetricNames() {
        if (metricNames == null) {
             metricNames = new ArrayList<String>();
             metricNames.add("utilization");
        }
        return metricNames;
    }

    public Double getMin(String metric) throws Exception {
        if (metric.equals("utilization")) {
            return 0.0;
        } else {
            throw new Exception("Invalid metric name");
        }
    }

    public Double getMax(String metric) throws Exception {
        if (metric.equals("utilization")) {
            return 1.0;
        } else {
            throw new Exception("Invalid metric name");
        }
    }

    public HashMap<String, CacheEntry>
        fetchValues(List<String> identifiers, Map<String, Object> options)
            throws Exception {

        System.out.println("base fetch start");
        return new HashMap<String, CacheEntry>();
    }

    public List<Timestep>
        getValues(String metric, List<String> identifiers,
                  Map<String, Object> options) throws Exception {

        LOG.info("getValues.start");
        if (!metric.equals("utilization")) {
            throw new Exception("Invalid metric name");
        }
        Boolean usePercentReserved = false;
        String metricType = "average";
        Integer timesteps = 1;
        if (options != null) {
            if (options.containsKey("usePercentReserved")) {
                usePercentReserved = (Boolean) options.get("usePercentReserved");
            }
            if (options.containsKey("metricType")) {
                metricType = (String) options.get("metricType");
            }
            if (options.containsKey("timesteps")) {
                timesteps = (Integer) options.get("timesteps");
            }
        }
        LOG.info("percentReserved: " + usePercentReserved);
        LOG.info("metricType: " + metricType);
        LOG.info("util timesteps: " + timesteps);
        HashMap<String, List<String>> ifceIdsToGetByName = new HashMap<String, List<String>>();
        for (String type : new String[] { Connection.IP, Connection.SDN, Connection.PEER } ) {
            for (Hub hub : domain.getHubs()) {
                for (AggrConnection ac : hub.getAggrConnections(type)) {
                    String ifceName = ac.getTopoId();
                    ifceIdsToGetByName.put(ifceName, namesToIfceIds.get(ifceName));
                }
                for (Node node: hub.getNodes()) {
                    for (AggrConnection ac : node.getAggrConnections(type)) {
                        String ifceName = ac.getTopoId();
                        ifceIdsToGetByName.put(ifceName, namesToIfceIds.get(ifceName));
                    }
                    for (IfceConnection ic: node.getIfceConnections(type)) {
                        String ifceName = ic.getTopoId();
                        ifceIdsToGetByName.put(ifceName, namesToIfceIds.get(ifceName));
                    }
                }
            }
        }
        ArrayList<String> ifceIds = new ArrayList<String>();
        Iterator<String> ifceNameIt = ifceIdsToGetByName.keySet().iterator();
        while (ifceNameIt.hasNext()) {
            String ifceName = ifceNameIt.next();
            List<String> ifceIdsByName = ifceIdsToGetByName.get(ifceName);
            if (ifceIdsByName != null) {
                for (String ifceId : ifceIdsByName) {
                    //LOG.info("ifce name: "+ifceName+" id: "+ifceId);
                }
                ifceIds.addAll(ifceIdsByName);
            }
        }
        HashMap<String, CacheEntry> stale = new HashMap<String, CacheEntry>();
        staleIds.clear();
        MetricCache cache = this.getCache();
        HashMap<String, CacheEntry> entries;
        if (cache != null) {
            entries = cache.getMetrics(ifceIds);
            for (String id : ifceIds) {
                if (!entries.keySet().contains(id)) {
                    staleIds.add(id);
                }
            }
            stale = this.getCache().getStale(staleIds);
            LOG.info("stale ids: "+staleIds.size());
            LOG.info("cached ids: "+entries.size());
        } else {
            entries = this.fetchValues(ifceIds, options);
        }

        HashMap<String, CacheEntry> mbpsByIfceId =
            new HashMap<String, CacheEntry>();
        mbpsByIfceId.putAll(entries);
        if (cache != null) {
            mbpsByIfceId.putAll(stale);
        }
        ArrayList<HashMap<String, HashMap<String, Double>>> utilSteps =
            new ArrayList<HashMap<String, HashMap<String, Double>>>();
        ArrayList<HashMap<String, HashMap<String, Double>>> mbpsSteps =
            new ArrayList<HashMap<String, HashMap<String, Double>>>();
        LOG.info("timesteps: " + timesteps);
        // inefficient but readable
        for (Integer ts=0; ts < timesteps; ts++) {
            // reset iterator
            ifceNameIt = ifceIdsToGetByName.keySet().iterator();
            HashMap<String, HashMap<String, Double>> namesToUtilization =
                new HashMap<String, HashMap<String, Double>>();
            HashMap<String, HashMap<String, Double>> namesToMbps =
                new HashMap<String, HashMap<String, Double>>();
            // calculate utilization;
            while (ifceNameIt.hasNext()) {
                String ifceName = ifceNameIt.next();
                if (ifceIdsToGetByName.get(ifceName) == null) {
                    continue;
                }
                Integer capacity = namesToCapacity.get(ifceName);
                // So many complex arguments to pass in duplication worth it.
                // In settings
                Double utilization = 0.0D;
                Double totalMbitsPs = 0.0D;
                if (usePercentReserved) {
                    String[] components = ifceName.split("--");
                    // SC09 a kludge, PEER's are not in reservation path
                    // and will not be in list of interface id's
                    if (components[0].equals("SC09") ||
                        components[1].equals("SC09") ||
                        components[2].equals("PEER")) {
                        // indicates to client to color it black
                        totalMbitsPs = -1000000.0D;
                    }
                }
                for (String ifceId : ifceIdsToGetByName.get(ifceName)) {
                    Double mbitsps = 0.0D;
                    if (mbpsByIfceId.get(ifceId) != null) {
                        HashMap<String,ArrayList<Double>> entry =
                            mbpsByIfceId.get(ifceId).getEntry();
                        ArrayList<Double> vals = entry.get("in");
                        if (vals.size() > ts) {
                            mbitsps = vals.get(ts);
                        } else {
                            mbitsps = 0.0D;
                        }
                    } else {
                        //LOG.info("no bps for " + ifceId);
                    }
                    totalMbitsPs += mbitsps;
                }
                utilization = totalMbitsPs / capacity;
                HashMap<String, Double> utilVal = new HashMap<String, Double>();
                utilVal.put("in", utilization);
                HashMap<String, Double> mbpsVal = new HashMap<String, Double>();
                mbpsVal.put("in", totalMbitsPs);
                if (!usePercentReserved) {
                    // out settings
                    utilization = 0.0D;
                    totalMbitsPs = 0.0D;
                    for (String ifceId : ifceIdsToGetByName.get(ifceName)) {
                        Double mbitsps = 0.0D;
                        if (mbpsByIfceId.get(ifceId) != null) {
                            HashMap<String,ArrayList<Double>> entry =
                                mbpsByIfceId.get(ifceId).getEntry();
                            ArrayList<Double> vals = entry.get("out");
                            if (vals.size() > ts) {
                                mbitsps = vals.get(ts);
                            } else {
                                mbitsps = 0.0D;
                            }
                        } else {
                            //LOG.info("no bps for "+ifceId);
                        }
                        totalMbitsPs += mbitsps;
                    }
                    utilization = totalMbitsPs / capacity;
                    utilVal.put("out", utilization);
                    mbpsVal.put("out", totalMbitsPs);
                }
                namesToUtilization.put(ifceName, utilVal);
                namesToMbps.put(ifceName, mbpsVal);
            }
            utilSteps.add(namesToUtilization);
            mbpsSteps.add(namesToMbps);
        }

        ArrayList<Timestep> results = new ArrayList<Timestep>();
        for (Integer tf = 0; tf < timesteps; tf++) {
            HashMap<String,HashMap<String,Double>> timestep =
                new HashMap<String,HashMap<String,Double>>();
            ifceNameIt = ifceIdsToGetByName.keySet().iterator();
            // prepare results ;
            while (ifceNameIt.hasNext()) {
                String ifceName = ifceNameIt.next();
                Connection c = ifceNamesToConnections.get(ifceName);
                Connection rev = c.getReverse();
                String revConnName = rev.getTopoId();
                HashMap<String, Double> result  = new HashMap<String, Double>();
                timestep.put(ifceName, result);
                result.put("capacity", namesToCapacity.get(ifceName).doubleValue());
                result.put("utilization_in", utilSteps.get(tf).get(ifceName).get("in"));
                result.put("mbps_in", mbpsSteps.get(tf).get(ifceName).get("in"));
                if (!usePercentReserved) {
                    result.put("utilization_out", utilSteps.get(tf).get(ifceName).get("out"));
                    result.put("mbps_out", mbpsSteps.get(tf).get(ifceName).get("out"));
                } else {
                    result.put("utilization_out", utilSteps.get(tf).get(revConnName).get("in"));
                    result.put("mbps_out", mbpsSteps.get(tf).get(revConnName).get("in"));
                }
            }
            Timestep frame = new Timestep();
            frame.setTimestep(timestep);
            results.add(frame);
        }
        LOG.info("getValues.finish");
        return results;
    }

    protected void initNameMaps() {
        namesToIfceIds = new HashMap<String, List<String>>();
        namesToCapacity = new HashMap<String, Integer>();
        ifceNamesToConnections = new HashMap<String, Connection>();

        Iterator<String> ifceNameIt;
        for (String type : new String[] { Connection.IP, Connection.SDN, Connection.PEER } ) {
            for (Hub hub : domain.getHubs()) {
                for (AggrConnection ac : hub.getAggrConnections(type)) {
                    String ifceName = ac.getTopoId();
                    namesToIfceIds.put(ifceName, ac.getIfceIds());
                    namesToCapacity.put(ifceName, ac.getCapacity());
                    ifceNamesToConnections.put(ifceName, ac);
                }
                for (Node node : hub.getNodes()) {
                    for (AggrConnection ac : node.getAggrConnections(type)) {
                        String ifceName = ac.getTopoId();
                        namesToIfceIds.put(ifceName, ac.getIfceIds());
                        namesToCapacity.put(ifceName, ac.getCapacity());
                        ifceNamesToConnections.put(ifceName, ac);
                    }
                    for (IfceConnection ic : node.getIfceConnections(type)) {
                        String ifceName = ic.getTopoId();
                        namesToIfceIds.put(ifceName, new ArrayList<String>());
                        namesToIfceIds.get(ifceName).add(ifceName);
                        namesToCapacity.put(ifceName, ic.getCapacity());
                        ifceNamesToConnections.put(ifceName, ic);
                    }
                }
            }
            ifceNameIt = namesToIfceIds.keySet().iterator();
            while (ifceNameIt.hasNext()) {
                String ifceName = ifceNameIt.next();
                List<String> ifceIds = namesToIfceIds.get(ifceName);
                ArrayList<String> newIfceIds = new ArrayList<String>();
                for (String ifceId : ifceIds) {
                    String[] parts = ifceId.split("--");
                    String newIfceId = parts[0];
                    newIfceIds.add(newIfceId);
                }
                namesToIfceIds.put(ifceName, newIfceIds);
            }
        }
    }

    public JSONObject convertId(String id, String direction, String metricType,
                                Integer startSeconds, Integer endSeconds,
                                Integer aggrSeconds, String uniqueId,
                                Integer timesteps) 
            throws JSONException {

        JSONObject obj = new JSONObject();
        String[] components = id.split(":");
        String uri = "/snmp/" + components[0] + "/interface/";
        String safeXface = components[1].replace("/", "_");
        uri += safeXface + "/" + direction;
        obj.put("uri", uri);
        obj.put("begin", String.valueOf(startSeconds));
        obj.put("end", String.valueOf(endSeconds));
        obj.put("calc", String.valueOf(aggrSeconds));
        obj.put("calc_func", metricType);
        obj.put("id", uniqueId);
        // kludge for SC09
        if (timesteps > 1) {
            // canned week
            if (aggrSeconds == 3600) {
                obj.put("cannedq", "3600");
            // canned month
            } else if (aggrSeconds == 14400) {
                obj.put("cannedq", "14400");
            // canned year
            } else if (aggrSeconds == 172800) {
                obj.put("cannedq", "172800");
            }
        }
        return obj;
    }

    public String getKey(JSONObject req) throws JSONException {
        // TODO:  error checking
        String uri = req.getString("uri");
        String[] components = uri.split("/");
        String routerName = components[2];
        String ifName = components[4].replace("_", "/");
        return routerName + ":" + ifName;
    }

    public String getDir(String uri) {
        // TODO:  error checking
        String[] components = uri.split("/");
        String dir = components[5];
        return dir;
    }

    public void putVal(JSONObject req,
                       HashMap<String, CacheEntry> fetchedValues,
                       JSONObject result) throws JSONException {

        JSONObject row = null;
        try {
            row = result.getJSONObject("result"); 
        } catch (JSONException e) {
            LOG.warn("JSONObject result null for" + req.getString("uri"));
        }
        if (row == null) {
            String error = result.getString("error");
            LOG.warn(error);
            if (error == null) {
                throw new JSONException("Unknown error on metrics server: " +
                                         error + " for " + req.getString("uri"));
            }
            return;
        }
        JSONArray dataArray = row.getJSONArray("data");
        ArrayList<Double> vals = new ArrayList<Double>();
        String fetchKey = getKey(req);
        if (dataArray.length() == 0) {
            LOG.info("**no data for ** " + fetchKey);
            vals.add(0.0D);
        }
        for (int i = 0; i < dataArray.length(); i++) {
            JSONArray valArray = dataArray.getJSONArray(i);
            Double val = valArray.getDouble(1);
            Double mbps = (val/1000000.0D)*8.0D;
            vals.add(mbps);
            //LOG.info("key: " + fetchKey + ", mbps: " + mbps);
        }
        String uri = req.getString("uri");
        CacheEntry fetched = fetchedValues.get(fetchKey);
        HashMap<String, ArrayList<Double>> entry;
        if (fetched == null) {
            fetched = new CacheEntry();
            entry = new HashMap<String, ArrayList<Double>>();
        } else {
            entry = fetched.getEntry();
        }
        String dir = getDir(uri);
        entry.put(dir, vals);
        fetched.setEntry(entry);
        fetchedValues.put(fetchKey, fetched);
    }
}
